﻿// Copyright richyp 2009
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace Richyp.Bodger {
    public enum UnitClass
    {
        Infantry,
        JumpInfantry,
        Cavalry,
        Beasts,
        Bikes,
        Jetbikes,
        MonsterousCreature,
        Artillery,
        Vehicle,
        Walker
    }
    public class Unit {
        public int ID { get; set; }
        public string Name { get; set; }
        public BaseUnit Base { get; set; }
        public int Maximum { get; set; }
        public int Minimum { get; set; }
        public int InitialQuantity { get; set; }
        public int BaseCost { get; set; }
        public int Increment { get; set; }
        public UnitClass Type { get; set; }

        private List<string> exclusiveList = new List<string>();
        public List<String> ExclusiveList {
            get {
                return this.exclusiveList;
            }
        }

        public List<ReplacementGroup> ReplacementGroups = new List<ReplacementGroup>();
        public Dictionary<string, int> AttributeOverrides = new Dictionary<string, int>();

        public static Unit Copy(Unit unit) {
            Unit newUnit = new Unit();
            newUnit.ID = unit.ID;
            newUnit.group = unit.group;
            newUnit.Name = unit.Name;
            newUnit.Base = unit.Base;
            newUnit.Maximum = unit.Maximum;
            newUnit.Minimum = unit.Minimum;
            newUnit.InitialQuantity = unit.InitialQuantity;
            newUnit.BaseCost = unit.BaseCost;
            newUnit.Increment = unit.Increment;
            newUnit.Type = unit.Type;

            foreach (string key in unit.AttributeOverrides.Keys) {
                newUnit.AttributeOverrides.Add(key, unit.AttributeOverrides[key]);
            }
            foreach (string exclusive in unit.exclusiveList) {
                newUnit.exclusiveList.Add(exclusive);
            }
            foreach (Equipment defaultEquipment in unit.DefaultEquipment) {
                Equipment e = new Equipment(defaultEquipment.Wargear, 0);
                e.MaximumPerSquad = defaultEquipment.MaximumPerSquad;
                e.Fixed = defaultEquipment.Fixed;
                e.Default = defaultEquipment.Default;
                e.Toggle = defaultEquipment.Toggle;
                e.Removes = defaultEquipment.Removes;
                newUnit.defaultEquipment.Add(e);
            }
            foreach (Equipment optionalEquipment in unit.OptionalEquipment) {
                Equipment e = new Equipment(optionalEquipment.Wargear, optionalEquipment.Cost);
                e.MaximumPerSquad = optionalEquipment.MaximumPerSquad;
                e.Fixed = optionalEquipment.Fixed;
                e.Default = optionalEquipment.Default;
                e.Toggle = optionalEquipment.Toggle;
                e.Removes = optionalEquipment.Removes;
                newUnit.optionalEquipment.Add(e);
            }
            foreach (ReplacementGroup group in unit.ReplacementGroups) {
                newUnit.ReplacementGroups.Add(group);
            }
            newUnit.ResetDefaultEquipment();
            return newUnit;
        }

        public int GetAttributeValue(string name) {
            int value = this.Base.GetAttributeValue(name);
            foreach (string attrName in AttributeOverrides.Keys) {
                if (attrName.ToUpper() == name.ToUpper()) {
                    value = AttributeOverrides[attrName];
                }
            }
            return value;
        }

        public string GetAttributeString(string name)
        {
            int baseValue = GetAttributeValue(name);
            int multiplier = 1;
            int modifier = 0;
            int CCWs = 0;
            int dCCWs = 0;
            int numDice = 0;
            int diceFacings = 0;
            int bestSave = baseValue;
            bool twoHanded = false;
            int powerFists = 0;
            int thunderHammers = 0;
            int lightningClaws = 0;

            // if no save is defined make it higher than 6
            if (bestSave < 1)
                bestSave = 7;

            foreach (Equipment eq in this.equipped)
            {
                foreach (AttributeModifier m in eq.Wargear.Modifiers)
                {
                    if (m.Attribute.ToUpper() == name.ToUpper())
                    {
                        if (name.ToUpper() == "SV" || name.ToUpper() == "INV" & m.Amount > 1)
                        {
                            bestSave = Math.Min(m.Amount, bestSave);
                            modifier -= m.Amount;
                        }

                        multiplier = m.Multiplier;
                        modifier += m.Amount;
                    }
                }

                if (name.ToUpper() == "A")
                {
                    if (eq.Wargear.IsCCW || eq.Wargear.Name.Equals("Close Combat Weapon") || eq.Wargear.Name.Equals("CCW"))
                    {
                        CCWs++;
                    }

                    if (eq.Wargear.IsDCCW || eq.Wargear.Name.Equals("Dreadnought Close Combat Weapon") || eq.Wargear.Name.Equals("DCCW"))
                    {
                        dCCWs++;
                    }

                    if (eq.Wargear.IsTwoHanded)
                    {
                        twoHanded = true;
                    }

                    if (eq.Wargear.IsPowerfist)
                    {
                        powerFists++;
                    }

                    if (eq.Wargear.IsLightningClaw)
                    {
                        lightningClaws++;
                    }

                    if (eq.Wargear.IsThunderHammer)
                    {
                        thunderHammers++;
                    }
                }
            }

            if (name.ToUpper() == "A")
            {
                if (CCWs > 1 && powerFists != 1 && lightningClaws != 1 && thunderHammers != 1 && twoHanded == false)
                {
                    modifier++;
                }

                if (dCCWs > 0)
                {
                    modifier += CCWs + dCCWs - 1;
                }
            }

            // No further work needed for armor/invulnerable saves
            if (name.ToUpper() == "SV" || name.ToUpper() == "INV")
            {
                // damn tzeentch
                if (bestSave == 7 && name.ToUpper() == "INV" && modifier == 1)
                    modifier = 2;

                bestSave -= modifier;

                if (bestSave == 7)
                {
                    if (name.ToUpper() == "INV" && modifier == 1)
                        modifier = 2; // damn tzeentch
                    if (name.ToUpper() == "INV")
                    {
                        return "";
                    }
                    return "-";
                }

                bestSave = Math.Max(2, bestSave);
                return bestSave.ToString() + "+";
            }


            int modifiedValue = baseValue * multiplier + modifier;

            if (modifiedValue == baseValue)
               return baseValue.ToString();

            if (modifiedValue < 1)
            {
                modifiedValue = 1;
            }

            if (name != "A" && modifiedValue > 10)
            {
                if (name != "FA" && name != "SA" && name != "RA")
                {
                    modifiedValue = 10;
                }
                else
                {
                    modifiedValue = Math.Min(modifiedValue, 14);
                }
            }

            return baseValue.ToString() + "(" + modifiedValue.ToString() + ")";

        }

        public int GetTotalCost() {
            int cost = this.BaseCost;           
            foreach (Equipment eq in this.equipped) {
                cost += eq.Cost;
            }
            return cost;
        }

        private string group = "none";
        public string Group {
            get {
                return this.group;
            }
            set {
                this.group = value;
            }
        }

        public static Unit CreateFromNode( CodexFile codexFile, XmlNode node) {
            Unit unit = new Unit();
            unit.Increment = 1;
            foreach (XmlAttribute attr in node.Attributes) {
                if (attr.Name.ToUpper() == "GROUP") {
                    unit.group = attr.Value;
                }
            }
            foreach (XmlNode childNode in node.ChildNodes) {
                string name = childNode.Name.ToUpper();
                string value = childNode.InnerText;
                if (name == "ID") {
                    unit.ID = int.Parse(value);
                } else if (name == "NAME") {
                    unit.Name = value;
                } else if (name == "BASE") {
                    int id = -1;
                    bool isNumber = int.TryParse(value, out id);
                    if (isNumber) {
                        Console.WriteLine(unit.Name + ": Got base unit from ID:" + id);
                        unit.Base = codexFile.GetBaseUnitByID(id);
                    } else {
                        Console.WriteLine(unit.Name + ": Got base unit from name:" + value);
                        unit.Base = codexFile.GetBaseUnitByName(value);
                    }
                } else if (name == "TYPE") {
                    switch (value.ToUpper())
                    {
                        case "JUMP" :
                        case "JUMP INFANTRY":
                        case "JUMPINFANTRY": unit.Type = UnitClass.JumpInfantry; break;
                        case "BIKE":
                        case "BIKER":
                        case "BIKES": unit.Type = UnitClass.Bikes; break;
                        case "JETBIKE":
                        case "JETBIKER":
                        case "JETBIKES": unit.Type = UnitClass.Jetbikes; break;
                        case "ARTILLERY" : unit.Type = UnitClass.Artillery; break;
                        case "BEAST":
                        case "BEASTS": unit.Type = UnitClass.Beasts; break;
                        case "CAVALRY": unit.Type = UnitClass.Cavalry; break;
                        case "MC":
                        case "MONSTEROUS CREATURE": unit.Type = UnitClass.MonsterousCreature; break;
                        case "TANK":
                        case "TRANSPORT":
                        case "VEHICLE": unit.Type = UnitClass.Vehicle; break;
                        case "WALKER": unit.Type = UnitClass.Walker; break;
                        default: unit.Type = UnitClass.Infantry; break;
                    }
                } else if (name == "QUANTITY") {
                    unit.InitialQuantity = int.Parse(value);
                } else if (name == "MINIMUM") {
                    unit.Minimum = int.Parse(value);
                } else if (name == "MAXIMUM") {
                    unit.Maximum = int.Parse(value);
                } else if( name == "INCREMENT" ) {
                    unit.Increment = int.Parse(value);
                } else if (name == "COST") {
                    unit.BaseCost = int.Parse(value);
                } else if(name == "EXCLUSIVITY" ) {
                    foreach (XmlNode exclusiveNode in childNode.ChildNodes) {
                        if (exclusiveNode.Name.ToUpper() == "NAMEDUNIT") {
                            unit.exclusiveList.Add(exclusiveNode.InnerText);
                        }
                    }
                } else if (name == "OVERRIDES") {
                    foreach( XmlNode overrideNode in childNode.ChildNodes ) {
                        string attrName = overrideNode.Name;
                        int attrValue = int.Parse(overrideNode.InnerText);
                        unit.AttributeOverrides.Add(attrName, attrValue);
                    }
                } else if (name == "EQUIPMENT") {
                    foreach (XmlNode equipmentNode in childNode.ChildNodes) {
                        string nodeName = equipmentNode.Name.ToUpper();
                        string nodeValue = equipmentNode.InnerText;
                        if (nodeName == "DEFAULT") {
                            Unit.ParseDefaultWargear(codexFile, unit, equipmentNode);
                        } else if (nodeName == "OPTIONS") {
                            Unit.ParseOptionalWargear(codexFile, unit, equipmentNode);
                        }
                    }
                }
            }
            return unit;
        }

        protected List<Equipment> defaultEquipment = new List<Equipment>();
        public List<Equipment> DefaultEquipment {
            get {
                return this.defaultEquipment;
            }
        }

        protected List<Equipment> optionalEquipment = new List<Equipment>();
        public List<Equipment> OptionalEquipment {
            get {
                return this.optionalEquipment;
            }
        }

        protected List<Equipment> equipped = new List<Equipment>();
        public List<Equipment> Equipped {
            get {
                return this.equipped;
            }
        }

        private static void ParseDefaultWargear(CodexFile codexFile, Unit unit, XmlNode node) {
            foreach (XmlNode childNode in node.ChildNodes) {
                string name = childNode.Name.ToUpper();
                string value = childNode.InnerText;
                if (name == "NAMEDWARGEARGROUP") {
                    WargearGroup group = codexFile.GetWargearGroupByName(value);
                    foreach (Wargear item in group.Wargear) {
                        unit.defaultEquipment.Add(new Equipment(item, 0));
                        unit.equipped.Add(new Equipment(item, 0));
                    }
                } else if (name == "NAMEDENTRY") {                    
                    Equipment equipment = new Equipment(codexFile.GetWargearByName(value), 0);
                    Equipment equipped = new Equipment(codexFile.GetWargearByName(value), 0);
                    foreach (XmlAttribute attr in childNode.Attributes) {
                        if (attr.Name.ToUpper() == "FIXED") {
                            equipment.Fixed = bool.Parse(attr.Value);
                            equipped.Fixed = bool.Parse(attr.Value);
                        }
                    }
                    equipped.Default = true;
                    equipment.Default = true;
                    unit.defaultEquipment.Add(equipment);
                    unit.equipped.Add( equipped);
                }
            }
        }

        private static void ParseOptionalWargear(CodexFile codexFile, Unit unit, XmlNode node) {
            foreach (XmlNode childNode in node.ChildNodes) {
                string name = childNode.Name.ToUpper();
                string value = childNode.InnerText;
                if (name == "ARMOURY") {
                    Dictionary<String, int> defaultItems = new Dictionary<string, int>();

                    foreach (XmlNode armouryNode in childNode.ChildNodes)
                    {
                        if (armouryNode.Name.ToUpper() == "DEFAULTITEM")
                        {
                            int cost = -1;
                            foreach (XmlAttribute attribute in armouryNode.Attributes)
                            {
                                if (attribute.Name.ToUpper() == "COST")
                                    cost = int.Parse(attribute.Value);
                            }
                            defaultItems.Add(armouryNode.InnerText, cost);
                        }
                    }

                    foreach (ArmouryItem armouryItem in codexFile.Armoury)
                    {
                        Wargear gear = armouryItem.wargear;
                        int cost = armouryItem.Cost;
                        int maximumPerSquad = 0;
                        bool defaultItem = false;
                        bool fixedItem = false;
                        bool toggle = true;

                        if (gear == null)
                        {
                            throw new Exception("Could not find gear: " + childNode.InnerText +
                                " in unit: " + unit.Name);
                        }

                        if (defaultItems.ContainsKey(armouryItem.wargear.Name))
                        {
                            defaultItem = true;
                            if (defaultItems[armouryItem.wargear.Name] > -1)
                                cost = defaultItems[armouryItem.wargear.Name];
                        }

                        Equipment equip = new Equipment(gear, cost);
                        if (maximumPerSquad == 0)
                        {
                            maximumPerSquad = unit.Maximum;
                        }
                        equip.Toggle = toggle;
                        equip.Fixed = fixedItem;
                        equip.Default = defaultItem;
                        equip.MaximumPerSquad = maximumPerSquad;
                        unit.optionalEquipment.Add(equip);
                        if (equip.Default)
                        {
                            Equipment equipped = new Equipment(gear, cost);
                            equipped.Fixed = false;
                            equipped.Toggle = toggle;
                            equipped.MaximumPerSquad = maximumPerSquad;
                            unit.equipped.Add(equipped);
                        }
                    }
                }

                if (name == "REPLACEMENTGROUP") {
                    Equipment defaultItem = null;
                    List<Equipment> equipment = new List<Equipment>();
                    List<DistinctEntry> distinctEntries = new List<DistinctEntry>();
                    List<Equipment> alternatives = new List<Equipment>();
                    foreach (XmlNode replacementNode in childNode.ChildNodes) {
                        string repNodeName = replacementNode.Name.ToUpper();
                        string repNodeValue = replacementNode.InnerText;
                        if (repNodeName == "DEFAULTENTRY") {
                            Equipment exists = null;
                            foreach (Equipment existing in unit.AllWargear) {
                                if (existing.Wargear.Name.ToUpper() == repNodeValue.ToUpper()) {
                                    exists = existing;
                                    break;
                                }
                            }
                            if (exists == null) {
                                throw new Exception("Need to add: " + repNodeValue + " to unit " + unit.Name + " before defining a replacement group");
                            }
                            defaultItem = exists;
                        } else if( repNodeName == "ALTERNATIVEREPLACEMENT" ) {
                            Equipment exists = null;
                            foreach (Equipment existing in unit.AllWargear) {
                                if (existing.Wargear.Name.ToUpper() == repNodeValue.ToUpper()) {
                                    exists = existing;
                                    break;
                                }
                            }
                            if (exists == null) {
                                throw new Exception("Need to add: " + repNodeValue + "to unit " + unit.Name + " before defining it as a replacement item");
                            }
                            alternatives.Add(exists);
                        } else if (repNodeName == "NAMEDENTRY") {
                            int cost = 0;
                            int maximum = 9999999;
                            string replaces = "";
                            foreach (XmlAttribute attr in replacementNode.Attributes) {
                                if (attr.Name.ToUpper() == "COST") {
                                    cost = int.Parse(attr.Value);
                                } else if (attr.Name.ToUpper() == "MAXIMUM") {
                                    maximum = int.Parse(attr.Value);
                                } else if (attr.Name.ToUpper() == "REMOVES") {
                                    replaces = attr.Value;
                                }
                            }
                            Equipment equipmentEntry = new Equipment(codexFile.GetWargearByName(repNodeValue), cost);
                            equipmentEntry.MaximumPerSquad = maximum;
                            equipmentEntry.Removes = replaces;
                            equipment.Add(equipmentEntry);
                        } else if (repNodeName == "DISTINCT") {
                            DistinctEntry distinctEntry = new DistinctEntry();
                            foreach (XmlAttribute attr in replacementNode.Attributes) {
                                if (attr.Name.ToUpper() == "MAXIMUM") {
                                    distinctEntry.Maximum = int.Parse(attr.Value);
                                } else if (attr.Name.ToUpper() == "REQUIRES") {
                                    distinctEntry.Requires = int.Parse(attr.Value);
                                }
                            }
                            foreach (XmlNode distinctNode in replacementNode.ChildNodes) {
                                string disNodeName = distinctNode.Name.ToUpper();
                                string disNodeValue = distinctNode.InnerText;
                                if (disNodeName == "NAMEDENTRY") {
                                    int cost = 0;
                                    int maximum = 9999;
                                    foreach (XmlAttribute attr in distinctNode.Attributes) {
                                        if (attr.Name.ToUpper() == "COST") {
                                            cost = int.Parse(attr.Value);
                                        } else if (attr.Name.ToUpper() == "MAXIMUM") {
                                            maximum = int.Parse(attr.Value);
                                        }
                                    }
                                    Wargear wargear = codexFile.GetWargearByName(disNodeValue);
                                    Equipment equipmentEntry = new Equipment(wargear, cost);
                                    equipmentEntry.MaximumPerSquad = maximum;
                                    distinctEntry.Options.Add(equipmentEntry);
                                }
                            }
                            distinctEntries.Add(distinctEntry);
                        }
                    }
                    ReplacementGroup group = null;
                    if (defaultItem != null) {
                        group = new ReplacementGroup(defaultItem, equipment);
                        foreach (Equipment alt in alternatives) {
                            group.Alternatives.Add(alt);
                        }
                    } else {
                        foreach (Equipment eq in unit.AllWargear) {
                            if (eq.Wargear.Name.ToUpper() == "NONE") {
                                defaultItem = eq;
                                break;
                            }
                        }
                        if (defaultItem == null) {
                            defaultItem = new Equipment(codexFile.GetWargearByName("None"), 0);
                            defaultItem.Default = true;                            
                            unit.optionalEquipment.Add(defaultItem);
                            group = new ReplacementGroup(defaultItem, equipment);
                        }
                    }
                    unit.ReplacementGroups.Add(group);
                    foreach (DistinctEntry distinctEntry in distinctEntries) {
                        group.DistinctEntries.Add(distinctEntry);
                    }
                } else if (name == "NAMEDENTRY") {
                    Wargear gear = codexFile.GetWargearByName(childNode.InnerText);
                    int cost = 0;
                    int maximumPerSquad = 0;
                    bool defaultItem = false;
                    bool fixedItem = false;
                    bool toggle = false;
                    foreach (XmlAttribute attr in childNode.Attributes) {
                        if (attr.Name.ToUpper() == "COST") {
                            cost = int.Parse(attr.Value);
                        } else if (attr.Name.ToUpper() == "MAXIMUM") {
                            maximumPerSquad = int.Parse(attr.Value);
                        } else if (attr.Name.ToUpper() == "DEFAULT") {
                            defaultItem = bool.Parse(attr.Value);
                            //fixedItem = true;
                        } else if (attr.Name.ToUpper() == "FIXED") {
                            fixedItem = bool.Parse(attr.Value);
                        } else if (attr.Name.ToUpper() == "TOGGLE") {
                            toggle = Boolean.Parse(attr.Value);
                        }
                    }
                    if (gear == null) {
                        throw new Exception("Could not find gear: " + childNode.InnerText +
                            " in unit: " + unit.Name);
                    }
                    Equipment equip = new Equipment(gear, cost);
                    if (maximumPerSquad == 0) {
                        maximumPerSquad = unit.Maximum;
                    }
                    equip.Toggle = toggle;
                    equip.Fixed = fixedItem;
                    equip.Default = defaultItem;
                    equip.MaximumPerSquad = maximumPerSquad;
                    unit.optionalEquipment.Add(equip);
                    if (equip.Default) {
                        Equipment equipped = new Equipment(gear, cost);
                        equipped.Fixed = false;
                        equipped.Toggle = toggle;
                        equipped.MaximumPerSquad = maximumPerSquad;
                        unit.equipped.Add(equipped);
                    }
                }
            }
           
        }

        public List<Equipment> AllWargear {
            get {
                List<Equipment> all = new List<Equipment>();
                foreach (Equipment equipment in this.optionalEquipment) {
                    all.Add(equipment);
                }
                foreach (Equipment equipment in this.defaultEquipment) {
                    all.Add(equipment);
                }
                foreach (ReplacementGroup group in this.ReplacementGroups) {
                    if (group != null) {
                        foreach (Equipment equipment in group.Options) {
                            all.Add(equipment);
                        }

                        foreach (DistinctEntry entry in group.DistinctEntries) {
                            foreach (Equipment equipment in entry.Options) {
                                all.Add(equipment);
                            }
                        }
                    }
                }
                return all;
            }
        }

        public void ResetDefaultEquipment() {
            this.equipped.Clear();
            foreach (Equipment equipment in this.defaultEquipment) {
                this.equipped.Add( new Equipment(equipment.Wargear, equipment.Cost));
            }
            foreach (Equipment equipment in this.optionalEquipment) {
                if (equipment.Default) {
                    this.equipped.Add( new Equipment(equipment.Wargear,equipment.Cost));
                }
            }
        }

        public string GetEquippedString() {
            string result = "";
            foreach (Equipment equipment in this.equipped) {
                result += equipment.Wargear.Name + ",";
            }
            if (result.Length > 0) {
                result = result.Substring(0, result.Length - 1);
            }
            return result;
        }

        public string ClassToString(UnitClass unitClass)
        {
            switch (unitClass)
            {
                case UnitClass.Artillery: return "Artillery";
                case UnitClass.Beasts: return "Beast";
                case UnitClass.Bikes: return "Biker";
                case UnitClass.Cavalry: return "Cavalry";
                case UnitClass.Infantry: return "Infantry";
                case UnitClass.Jetbikes: return "Jetbiker";
                case UnitClass.JumpInfantry: return "Jump Infantry";
                case UnitClass.MonsterousCreature: return "Monsterous Creature";
                case UnitClass.Vehicle: return "Vehicle";
                case UnitClass.Walker: return "Walker";
            }

            return "Infantry";
        }

    }

    public class Equipment {
        public Equipment(Wargear wargear, int cost) {
            this.Cost = cost;
            this.Fixed = false;
            this.Wargear = wargear;
            this.Default = false;
            this.Toggle = false;
            this.MaximumPerSquad = 999999;
            this.ReplacementGroups = new List<ReplacementGroup>();
        }

        public static Equipment Copy(Equipment eq)
        {
            Equipment newEq = new Equipment(eq.Wargear, eq.Cost);
            newEq.MaximumPerSquad = eq.MaximumPerSquad;
            newEq.Fixed = eq.Fixed;
            newEq.Default = eq.Default;
            newEq.Toggle = eq.Toggle;
            newEq.Removes = eq.Removes;

            return newEq;
        }

        public bool Toggle { get; set; }
        public bool Fixed { get; set; }
        public int Cost { get; set; }
        public Wargear Wargear { get; set; }
        public int MaximumPerSquad { get; set; }
        public List<ReplacementGroup> ReplacementGroups { get; set; }
        public bool Default { get; set; }
        public string Removes { get; set; }
        //public static Equipment Copy(Equipment eq) {
        //    return eq;
        //}

        public override string ToString()
        {
            return Wargear.ToString();
        }
    }

}
